﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Drawing;


namespace M3G {
    public class ImageCube : ImageBase {
        #region Constant
        public const int PosX = 56;
        public const int NegX = 57;
        public const int PosY = 58;
        public const int NegY = 59;
        public const int PosZ = 60;
        public const int NegZ = 61;
        #endregion


        #region Method
        /**
         * 白紙のキューブマップを作るコンストラクター
         * */
        public ImageCube (int format, int size)
            : base (format, size, size) {
            AllocateImage (PosX);
            AllocateImage (NegX);
            AllocateImage (PosY);
            AllocateImage (NegY);
            AllocateImage (PosZ);
            AllocateImage (NegZ);
        }

        /**
         * 指定されたミップマップ階層x6を持つキューブマップを作るコンストラクター
         * */
        public ImageCube (int format, int size, byte[][][] faces)
            : this (format, size) {
            if (faces.Length < 6) {
                throw new AggregateException ("Cube map need 6 faces, f=" + faces.Length);
            }
            var requiredMipmap = MaxMipLevel + 1;
            for (var f=0; f < 6; f++) {
                if (faces[f].Length < requiredMipmap) {
                    throw new ArgumentException ("Mipmap pyramid is less than required, " + faces[f].Length + ",req=" + requiredMipmap);
                }
                for (var m=0; m <= MaxMipLevel; m++) {
                    var requiredLengh = MiplevelWidth (m) * MiplevelHeight (m) * Bpp;
                    if (faces[f][m].Length < requiredLengh) {
                        throw new ArgumentException ("Length of m'th mipmap is less than required");
                    }
                }
            }
            for (var m=0; m <= MaxMipLevel; m++) {
                SetPixels (PosX, m, faces[0][m]);
                SetPixels (NegX, m, faces[1][m]);
                SetPixels (PosY, m, faces[2][m]);
                SetPixels (NegY, m, faces[3][m]);
                SetPixels (PosZ, m, faces[4][m]);
                SetPixels (NegZ, m, faces[5][m]);
            }
        }

        /**
         * コンストラクターのテスト2
         * 6つのBitmapオブジェクトで指定されたキューブマップを作るコンストラクター
         * */
        public ImageCube (int format, Bitmap[] faces)
            : this (format, (faces[0] == null) ? 0 : faces[0].Width) {
            if (faces.Length < 6) {
                throw new AggregateException ("Cube map need 6 faces, f=" + faces.Length);
            }
            if (faces.Contains(null)) {
                throw new ArgumentNullException("Face is null");
            }
            var size = faces[0].Width;
            if (Array.Exists(faces, x => x.Width != x.Height)) {
                throw new ArgumentException ("Image size must be square");
            }
            if (Array.Exists(faces, x => x.Width != size)) {
                throw new ArgumentException ("Image size of all faces must be equal");
            }

            SetPixels (PosX, 0, ConvertToPixels (format, faces[0]));
            SetPixels (NegX, 0, ConvertToPixels (format, faces[1]));
            SetPixels (PosY, 0, ConvertToPixels (format, faces[2]));
            SetPixels (NegY, 0, ConvertToPixels (format, faces[3]));
            SetPixels (PosZ, 0, ConvertToPixels (format, faces[4]));
            SetPixels (NegZ, 0, ConvertToPixels (format, faces[5]));
        }

        /**
         * コピーコンストラクター
         * */
        protected ImageCube (ImageCube image)
            : base (image) {
        }

        /**
         * 画像の1部を指定のデータで置き換える。
         * byte[]で指定
         * メモ: 入力はフォーマットにかかわらず常に1バイト1コンポーネント
         * */
        public void Set (int face, int miplevel, int x, int y, int width, int height, byte[] image) {
            if (image == null) {
                throw new ArgumentNullException ("Image is null");
            }
            if (Mutable == false) {
                throw new ArgumentException ("This image is not mutable");
            }
            if ((Option & NoMipmap) != 0 && miplevel != 0) {
                throw new ArgumentException ("This image is not mipmapped, miplevel=" + miplevel);
            }
            if (miplevel < 0 || miplevel > MaxMipLevel) {
                throw new ArgumentException ("Miplevel must be within [0," + MaxMipLevel + "]");
            }
            if (face < PosX || face > NegZ) {
                throw new ArgumentException ("Face must be one of symblic constant defined in ImageCube");
            }
            if (width < 0 || x + width > MiplevelWidth (miplevel) || height < 0 || y + height > MiplevelHeight (miplevel)) {
                throw new ArgumentException ("Specified rectangle is out of region");
            }
            var required = width * height * Bpp;
            if (image.Length < required) {
                throw new ArgumentException ("Length of Image.Length is less than required, len=" + image.Length + ",req=" + required);
            }

            SetPixels (face, miplevel, x, y, width, height, image);
        }

        /**
         * 画像の1部を書き換える。
         * ushort[]で指定
         * */
        public void Set (int face, int miplevel, int x, int y, int width, int height, ushort[] image) {
            if (image == null) {
                throw new ArgumentNullException ("Image is null");
            }
            if (Mutable == false) {
                throw new ArgumentException ("This image is not mutable");
            }
            if ((Option & NoMipmap) != 0 && miplevel != 0) {
                throw new ArgumentException ("This image is not mipmapped, miplevel=" + miplevel);
            }
            if (miplevel < 0 || miplevel > MaxMipLevel) {
                throw new ArgumentException ("Miplevel must be within [0," + MaxMipLevel + "]");
            }
            if (face < PosX || face > NegZ) {
                throw new ArgumentException ("Face must be one of symblic constant defined in ImageCube");
            }
            if (width < 0 || x + width > MiplevelWidth (miplevel) || height < 0 || y + height > MiplevelHeight (miplevel)) {
                throw new ArgumentException ("Specified rectangle is out of region");
            }
            var required = width * height;
            if (image.Length < required) {
                throw new ArgumentException ("Length of Image.Length is less than required, len=" + image.Length + ",req=" + required);
            }

            var pixels = ConvertToPixels (Format, image);

            SetPixels (face, miplevel, x, y, width, height, pixels);
        }

        /**
         * 画像の1部を書き換える。
         * uint[]で指定
         * */
        public void Set (int face, int miplevel, int x, int y, int width, int height, uint[] image) {
            if (image == null) {
                throw new ArgumentNullException ("Image is null");
            }
            if (Mutable == false) {
                throw new ArgumentException ("This image is not mutable");
            }
            if ((Option & NoMipmap) != 0 && miplevel != 0) {
                throw new ArgumentException ("This image is not mipmapped, miplevel=" + miplevel);
            }
            if (miplevel < 0 || miplevel > MaxMipLevel) {
                throw new ArgumentException ("Miplevel must be within [0," + MaxMipLevel + "]");
            }
            if (face < PosX || face > NegZ) {
                throw new ArgumentException ("Face must be one of symblic constant defined in ImageCube");
            }
            if (width < 0 || x + width > MiplevelWidth (miplevel) || height < 0 || y + height > MiplevelHeight (miplevel)) {
                throw new ArgumentException ("Specified rectangle is out of region");
            }
            var required = width * height;
            if (image.Length < required) {
                throw new ArgumentException ("Length of Image.Length is less than required, len=" + image.Length + ",req=" + required);
            }

            var pixels = ConvertToPixels (Format, image);

            SetPixels (face, miplevel, x, y, width, height, pixels);
        }

        /**
         * 画像の1部を書き換える。
         * Bitmapで指定
         * */
        public void Set (int face, int miplevel, int x, int y, int srcX, int srcY, int width, int height, Bitmap image) {
            if (image == null) {
                throw new ArgumentNullException ("Image is null");
            }
            if (Mutable == false) {
                throw new ArgumentException ("This image is not mutable");
            }
            if ((Option & NoMipmap) != 0 && miplevel != 0) {
                throw new ArgumentException ("This image is not mipmapped, miplevel=" + miplevel);
            }
            if (miplevel < 0 || miplevel > MaxMipLevel) {
                throw new ArgumentException ("Miplevel must be within [0," + MaxMipLevel + "]");
            }
            if (face < PosX || face > NegZ) {
                throw new ArgumentException ("Face must be one of symblic constant defined in ImageCube");
            }
            if (width < 0 || x + width > MiplevelWidth (miplevel) || height < 0 || y + height > MiplevelHeight (miplevel)) {
                throw new ArgumentException ("Specified rectangle is out of region");
            }
            if (srcX + width > image.Width || srcY + height > image.Height) {
                throw new ArgumentException ("Specified src image is out of region");
            }

            var pixels = ConvertToPixels (Format, srcX, srcY, width, height, image);

            SetPixels (face, miplevel, x, y, width, height, pixels);
        }

        public override Object3D Duplicate () {
            return new ImageCube (this);
        }


        #endregion

    }
}
